package com.xbongbong.saas.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.domain.entity.ext.PaasFormDataEntityExt;
import com.xbongbong.paas.elasticsearch.util.EsHelper;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.enums.IndexTypeEnum;
import com.xbongbong.paas.help.ProFormHelp;
import com.xbongbong.paas.model.PaasFormExplainModel;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.DateUtil;
import com.xbongbong.paas.toolbox.wrap.BaseVO;
import com.xbongbong.pro.domain.entity.ScoreRuleEntity;
import com.xbongbong.pro.domain.entity.UpdateDataEntity;
import com.xbongbong.pro.domain.entity.UpdateDataValueEntity;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.SaasMarkEnum;
import com.xbongbong.pro.enums.errorcodes.ScoreErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.score.pojo.dto.DataScoreUpdateBatchDTO;
import com.xbongbong.pro.score.pojo.dto.DataScoreUpdateSingleDTO;
import com.xbongbong.pro.score.pojo.dto.ScoreRabbitMqDTO;
import com.xbongbong.pro.score.pojo.vo.DataScoreUpdateBatchVO;
import com.xbongbong.pro.score.pojo.vo.DataScoreUpdateSingleVO;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.ext.ClueEntityExt;
import com.xbongbong.saas.domain.entity.ext.CustomerEntityExt;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.ClueEnum;
import com.xbongbong.saas.enums.business.CustomerManagementEnum;
import com.xbongbong.saas.help.MarketManagementBusinessHelp;
import com.xbongbong.saas.help.ScoreCalculateHelp;
import com.xbongbong.saas.model.ClueModel;
import com.xbongbong.saas.model.CustomerModel;
import com.xbongbong.saas.model.IndexTypeModel;
import com.xbongbong.saas.model.ScoreRuleModel;
import com.xbongbong.saas.service.ScoreCalculateService;
import org.apache.commons.collections4.CollectionUtils;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.xbongbong.saas.toolbox.help.FastJsonHelper.getDoubleOrDefaultFromFormData;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;

/**
 * @author 秦春阳
 */
@Service("scoreCalculateService")
public class ScoreCalculateServiceImpl implements ScoreCalculateService {

    private static final Logger LOG = LoggerFactory.getLogger(ScoreCalculateServiceImpl.class);

    @Resource
    private ScoreCalculateHelp scoreCalculateHelp;
    @Resource
    private ClueModel clueModel;
    @Resource
    private CustomerModel customerModel;
    @Resource
    private EsHelper esHelper;
    @Resource
    private ProFormHelp proFormHelp;
    @Resource
    private ScoreRuleModel scoreRuleModel;
    @Resource
    private PaasFormExplainModel paasFormExplainModel;
    @Resource
    private MarketManagementBusinessHelp marketManagementBusinessHelp;
    @Resource
    private IndexTypeModel indexTypeModel;

    /**
     * 异步更新数据评分
     *
     * @param xbbRefTypeEnum
     * @param corpid
     * @param dataId
     * @param paasFormDataEntityExt
     */
    @Async(value = "scoreThreadPool")
    @Override
    public void asyncUpdateSingleDataScore(XbbRefTypeEnum xbbRefTypeEnum, String corpid, Long dataId, PaasFormDataEntityExt paasFormDataEntityExt) {
        try {
            //套餐是标准版,不计算评分
            if (marketManagementBusinessHelp.isStandardWithFeeType(corpid)) {
                return;
            }
            //如果paasFormDataEntityExt 是空的, 那就根据 dataId 再去查一遍
            if (Objects.isNull(paasFormDataEntityExt) && Objects.nonNull(dataId)) {
                //如果传输的是dataId，需要再重新一遍,为了避免查询到老数据（比如：第一次新建跟进记录修改状态的场景）,添加0.3s的延时
                Thread.sleep(300);
                switch (xbbRefTypeEnum) {
                    case CUSTOMER_MANAGEMENT:
                        paasFormDataEntityExt = customerModel.getByKey(dataId, corpid);
                        break;
                    case CLUE:
                        paasFormDataEntityExt = clueModel.getByKey(dataId, corpid);
                        break;
                    default:
                        //评分业务类型错误
                        throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
                }
            }
            singleDataScoreUpdate(corpid, xbbRefTypeEnum, paasFormDataEntityExt);
        } catch (Exception e) {
            LOG.error("asyncUpdateSingleDataScore 异步计算评分异常,异常原因-{}-", e.getMessage());
        }
    }

    /**
     * 异步更新批量数据评分
     * 注意： 评分的批量数据查询时，除了"重新计算"脚本，其他的直接查询数据库。es的数据更新时有延时的，查询es会导致一些评分计算的业务场景不是最新的
     *
     * @param xbbRefTypeEnum
     * @param corpid
     */
    @Async(value = "scoreThreadPool")
    @Override
    public void asyncUpdateBatchDataScore(XbbRefTypeEnum xbbRefTypeEnum, String corpid, List<Long> idIn) {
        try {
            //套餐是标准版 或者 “市场管理”应用未开启，不计算评分;
            if (marketManagementBusinessHelp.feeTypeIsStandardOrAppIsClosed(corpid)) {
                return;
            }
            List<PaasFormDataEntityExt> paasFormDataEntityExtList = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(idIn)) {
                Map<String, Object> param = new HashMap<>(4);
                param.put("corpid", corpid);
                param.put("idIn", idIn);
                param.put("del", 0);
                switch (xbbRefTypeEnum) {
                    case CUSTOMER_MANAGEMENT:
                        List<CustomerEntityExt> customerEntityExtList = customerModel.findEntitys(param);
                        //集合拷贝
                        if (CollectionUtils.isNotEmpty(customerEntityExtList)) {
                            PaasFormDataEntityExt paasFormDataEntityExt;
                            for (CustomerEntityExt customerEntityExt : customerEntityExtList) {
                                paasFormDataEntityExt = new PaasFormDataEntityExt();
                                BeanUtil.copyProperties(customerEntityExt, paasFormDataEntityExt);
                                paasFormDataEntityExtList.add(paasFormDataEntityExt);
                            }
                        }
                        break;
                    case CLUE:
                        List<ClueEntityExt> clueList = clueModel.findEntitys(param);
                        BeanUtil.copyPropertiesList(clueList, paasFormDataEntityExtList, PaasFormDataEntityExt.class);
                        break;
                    default:
                        //评分业务类型错误
                        throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
                }
            } else {
                //参数缺失
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100002, SystemErrorCodeEnum.API_ERROR_100002.getMsg());
            }
            batchDataScoreUpdate(corpid, xbbRefTypeEnum, paasFormDataEntityExtList);
        } catch (Exception e) {
            LOG.error("asyncUpdateSingleDataScore 异步计算评分异常,异常原因-{}-", e.getMessage());
        }
    }

    /**
     * 单条数据计算评分，该条数据主要用于业务编辑，新增时，计算单条数据的评分
     * 这个方法的调用都是在业务编辑，新增的时候，异步处理评分，为了让评分计算不影响主流程，在这里捕获了所有的异常
     *
     * @return
     * @throws XbbException
     */
    @Override
    public BaseVO singleDataScoreUpdate(String corpid, XbbRefTypeEnum xbRefTypeEnum, PaasFormDataEntityExt paasFormDataEntity) {
        BaseVO baseVO = new BaseVO();
        //这个方法的调用都是在业务编辑，新增的时候，异步处理评分，为了让评分计算不影响主流程，在这里捕获了所有的异常
        try {
            Long formId = paasFormDataEntity.getFormId();
            //查询表单解释
            PaasFormExplainEntity paasFormExplainEntity = paasFormExplainModel.getByFormId(formId,corpid);
            //查询表单下启用的评分规则
            ScoreRuleEntity enableScoreRuleEntity = scoreRuleModel.getByFormId(formId, corpid);
            //计算分数
            Double dataScore = scoreCalculateHelp.calculateSingleDataScore(xbRefTypeEnum, paasFormDataEntity,paasFormExplainEntity,enableScoreRuleEntity);
            //分数值为空，不更新数据;不为空则更新数据
            if (Objects.nonNull(dataScore)) {
                JSONObject data = paasFormDataEntity.getData();
                String scoreAttr;
                Double oldScoreValue;
                switch (xbRefTypeEnum) {
                    case CUSTOMER_MANAGEMENT:
                        try {
                            scoreAttr = CustomerManagementEnum.SCORE.getAttr();
                            oldScoreValue = getDoubleOrDefaultFromFormData(data,scoreAttr,null);
                            //更新的分数 和 旧的分数不一样才需要更新数据.
                            if (Objects.isNull(oldScoreValue) || !Objects.equals(oldScoreValue,dataScore)) {
                                paasFormDataEntity.getData().put(scoreAttr,dataScore);
                                batchUpdate(XbbRefTypeEnum.CUSTOMER_MANAGEMENT,corpid,Arrays.asList(paasFormDataEntity),scoreAttr);
                            }
                        } catch (Exception e) {
                            //这边如果有脏数据的话，可能出现 oldScoreValue 不是Number型的.(兼容一些特殊场景可能出现的错误)
                            LOG.error("评分计算消费发送异常, ScoreCalculateServiceImpl singleDataScoreUpdate ,异常数据 data -{}-,异常原因-{}-", JSON.toJSONString(data), e.getMessage());
                        }
                        break;
                    case CLUE:
                        try {
                            scoreAttr = ClueEnum.SCORE.getAttr();
                            //default不要取0. 0和null是有区别的
                            oldScoreValue = getDoubleOrDefaultFromFormData(data,scoreAttr,null);
                            // 需要更新数据的场景（1）新分值不为空，但是老数据旧的分值为 null, 或者(2) 新的分数 和 旧的分数不一样
                            if (Objects.isNull(oldScoreValue) || !Objects.equals(oldScoreValue,dataScore)) {
                                paasFormDataEntity.getData().put(scoreAttr,dataScore);
                                batchUpdate(XbbRefTypeEnum.CLUE,corpid,Arrays.asList(paasFormDataEntity),scoreAttr);
                            }
                        } catch (Exception e) {
                            //这边如果有脏数据的话，可能出现 oldScoreValue 不是Number型的.(兼容API那边可能出现的错误场景)
                            LOG.error("评分计算消费发送异常, ScoreCalculateServiceImpl singleDataScoreUpdate ,异常数据 data -{}-,异常原因-{}-", JSON.toJSONString(data), e.getMessage());
                        }
                        break;
                    default:
                        throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200011, "该业务类型暂不支持评分");
                }
            }
        } catch (Exception e) {
            LOG.error("评分计算失败，失败入参 corpid -{}-,paasFormDataEntity-{}-", corpid, JSON.toJSONString(Objects.nonNull(paasFormDataEntity) ? paasFormDataEntity : ""));
        }
        return baseVO;
    }

    @Override
    public BaseVO batchDataScoreUpdate(String corpid, XbbRefTypeEnum xbRefTypeEnum, List<PaasFormDataEntityExt> paasFormDataEntityList) {
        try {
            if (Objects.nonNull(corpid) && CollectionUtils.isNotEmpty(paasFormDataEntityList)) {
                //填充分值
                String scoreAttr = setScore2DataList(corpid, xbRefTypeEnum, paasFormDataEntityList);
                if(Objects.nonNull(scoreAttr)){
                    //更新数据
                    batchUpdate(xbRefTypeEnum, corpid, paasFormDataEntityList, scoreAttr);
                }
            }
        } catch (Exception e) {
            LOG.error("batchDataScoreUpdate 发生异常");
        }
        return new BaseVO();
    }

    @Override
    public void batchDataScoreUpdateForScript(ScoreRabbitMqDTO scoreRabbitMqDTO) throws XbbException {
        List<Long> dataIdList = scoreRabbitMqDTO.getDataIdList();
        String corpid = scoreRabbitMqDTO.getCorpid();
        Integer businessType = scoreRabbitMqDTO.getBusinessType();
        IndexTypeEnum indexTypeEnum;
        String scoreAttr = null;
        XbbRefTypeEnum xbRefTypeEnum = XbbRefTypeEnum.getByCode(businessType);
        switch (xbRefTypeEnum) {
            case CUSTOMER_MANAGEMENT:
                indexTypeEnum = indexTypeModel.getCustomerIndex(corpid, XbbRefTypeEnum.CUSTOMER_MANAGEMENT.getCode(), SaasMarkEnum.SAAS.getCode());;
                scoreAttr = CustomerManagementEnum.SCORE.getAttr();
                break;
            case CLUE:
                indexTypeEnum = IndexTypeEnum.IDX_SAAS_CLUE;
                scoreAttr = ClueEnum.SCORE.getAttr();
                break;
            default:
                throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
        }
        if (CollectionUtils.isEmpty(dataIdList) || Objects.isNull(corpid) || Objects.isNull(businessType)) {
            LOG.error("batchDataScoreUpdateForScript 参数异常");
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001);
        }
        //查询出数据
        List<PaasFormDataEntityExt> paasFormDataEntityExtList = getDataList(indexTypeEnum, corpid, dataIdList);
        //填充分值
        setScore2DataList(corpid,xbRefTypeEnum,paasFormDataEntityExtList);
        //批量更新
        batchUpdate(xbRefTypeEnum, corpid, paasFormDataEntityExtList, scoreAttr);
    }

    @Override
    public DataScoreUpdateBatchVO updateBatch(DataScoreUpdateBatchDTO dataScoreUpdateBatchDTO) throws XbbException {
        try {
            if (CollectionsUtil.isEmpty(dataScoreUpdateBatchDTO.getUpdateIdList())) {
                return new DataScoreUpdateBatchVO();
            }
            Integer businessType = dataScoreUpdateBatchDTO.getBusinessType();
            XbbRefTypeEnum xbRefTypeEnum = XbbRefTypeEnum.getByCode(businessType);
            asyncUpdateBatchDataScore(xbRefTypeEnum, dataScoreUpdateBatchDTO.getCorpid(), dataScoreUpdateBatchDTO.getUpdateIdList());
        } catch (Exception e) {
            LOG.error("com.xbongbong.saas.service.impl.ScoreCalculateServiceImpl.updateBatch", e);
        }
        return new DataScoreUpdateBatchVO();
    }

    @Override
    public DataScoreUpdateSingleVO updateSingle(DataScoreUpdateSingleDTO dataScoreUpdateSingleDTO) throws XbbException {
        try {
            if (Objects.isNull(dataScoreUpdateSingleDTO.getDataId())) {
                return new DataScoreUpdateSingleVO();
            }
            Integer businessType = dataScoreUpdateSingleDTO.getBusinessType();
            XbbRefTypeEnum xbRefTypeEnum = XbbRefTypeEnum.getByCode(businessType);
            asyncUpdateSingleDataScore(xbRefTypeEnum, dataScoreUpdateSingleDTO.getCorpid(), dataScoreUpdateSingleDTO.getDataId(), dataScoreUpdateSingleDTO.getPaasFormDataEntityExt());
        } catch (Exception e) {
            LOG.error("com.xbongbong.saas.service.impl.ScoreCalculateServiceImpl.updateSingle", e);
        }
        return new DataScoreUpdateSingleVO();
    }

    /**
     * 设置分值给 paasFormDataEntityExtList,并且返回 scoreAttr
     * 该方法指给类内部调用，需要改为public时，请加上参数空校验
     * @param corpid
     * @param xbRefTypeEnum 业务类型
     * @param paasFormDataEntityExtList
     */
    private String setScore2DataList(String corpid, XbbRefTypeEnum xbRefTypeEnum,List<PaasFormDataEntityExt> paasFormDataEntityExtList) throws XbbException {
        //评分字段的attr
        String scoreAttr = null;
        if(CollectionUtils.isNotEmpty(paasFormDataEntityExtList)){
            switch (xbRefTypeEnum) {
                case CUSTOMER_MANAGEMENT:
                    scoreAttr = CustomerManagementEnum.SCORE.getAttr();
                    break;
                case CLUE:
                    scoreAttr = ClueEnum.SCORE.getAttr();
                    break;
                default:
                    throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
            }
            //查询表单解释
            List<Long> formIdList = paasFormDataEntityExtList.stream().map(item -> item.getFormId()).collect(Collectors.toList());
            Map<Long, PaasFormExplainEntity> formExplainMap = proFormHelp.getFormExplainMap(formIdList, corpid);
            //查询启动的评分规则
            Map<Long, ScoreRuleEntity> scoreRuleMap = getEnableScoreRuleMap(formIdList, corpid);
            //倒序删除
            PaasFormDataEntityExt paasFormData;
            for (int i = paasFormDataEntityExtList.size() - 1; i >= 0; i--) {
                paasFormData = paasFormDataEntityExtList.get(i);
                Long formId = paasFormData.getFormId();
                //计算出新的分数值
                Double newDataScore = scoreCalculateHelp.calculateSingleDataScore(xbRefTypeEnum, paasFormData,formExplainMap.get(formId),scoreRuleMap.get(formId));
                //获取老的分数值
                Object oldDataScoreObj = paasFormData.getData().get(scoreAttr);
                Double oldDataScore = Objects.isNull(oldDataScoreObj) ? null : ((Number) oldDataScoreObj).doubleValue();
                //以下两种情况不做更新 (1)新分值为null (2) 新的分值等于老的分值
                if (Objects.isNull(newDataScore) || newDataScore.equals(oldDataScore)) {
                    paasFormDataEntityExtList.remove(i);
                    continue;
                }
                paasFormData.getData().put(scoreAttr, newDataScore);
            }
        }
        return scoreAttr;
    }

    /**
     * 获取表单对应的启用的评分规则
     * key是 formId，value该表单对应启用的评分规则
     * @return
     */
    public Map<Long, ScoreRuleEntity> getEnableScoreRuleMap(List<Long> formIdsList ,String corpid) {
        Map<Long, ScoreRuleEntity> scoreRuleEntityMap = new HashMap<>(formIdsList.size());
        if (formIdsList.isEmpty()) {
            return scoreRuleEntityMap;
        }
        if (!formIdsList.isEmpty()) {
            Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            param.put("formIdIn", formIdsList);
            param.put(StringConstant.CORPID, corpid);
            param.put(StringConstant.DEL, DelEnum.NORMAL.getDel());
            param.put("enable", 1);
            List<ScoreRuleEntity> scoreRuleEntityList = scoreRuleModel.findEntitys(param);
            scoreRuleEntityList.forEach((item)->{
                scoreRuleEntityMap.put(item.getFormId(), item);
            });
        }
        return scoreRuleEntityMap;
    }

    /**
     * 查询数据
     */
    private List<PaasFormDataEntityExt> getDataList(IndexTypeEnum indexTypeEnum, String corpid, List<Long> dataIdList) throws XbbException {
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery(StringConstant.CORPID + StringConstant.POINT + StringConstant.KEY_WORD, corpid));
        boolQueryBuilder.filter(termsQuery(StringConstant.DATA_ID, dataIdList));
        boolQueryBuilder.filter(termQuery(StringConstant.DEL, DelEnum.NORMAL.getDel()));
        List<PaasFormDataEntityExt> dataEntityExts = esHelper.findByScroll(indexTypeEnum, boolQueryBuilder, PaasFormDataEntityExt.class, null);
        return dataEntityExts;
    }

    /**
     * 批量更新分值
     */
    private void batchUpdate(XbbRefTypeEnum xbRefTypeEnum, String corpid, List<PaasFormDataEntityExt> paasFormDataEntityExtList, String scoreAttr) throws XbbException {
        if (CollectionUtils.isNotEmpty(paasFormDataEntityExtList)) {
            //批量更新(只更新评分字段)
            List<UpdateDataEntity> updateDataEntityList = new ArrayList<>();
            UpdateDataValueEntity updateDataValueEntity;
            UpdateDataEntity updateDataEntity;
            for (PaasFormDataEntityExt paasFormDataEntityExt : paasFormDataEntityExtList) {
                updateDataEntity = new UpdateDataEntity();
                updateDataValueEntity = new UpdateDataValueEntity();
                updateDataEntity.setId(paasFormDataEntityExt.getId());
                updateDataEntity.setCorpid(corpid);
                updateDataValueEntity.setKey(scoreAttr);
                updateDataValueEntity.setEsKey(scoreAttr);
                updateDataValueEntity.setValue(paasFormDataEntityExt.getData().get(scoreAttr));
                updateDataEntity.setData(Arrays.asList(updateDataValueEntity));
                //更新时间不能为空
                updateDataEntity.setUpdateTime(DateUtil.getNow());
                updateDataEntityList.add(updateDataEntity);
            }
            switch (xbRefTypeEnum) {
                case CUSTOMER_MANAGEMENT:
                    customerModel.updateBatch(updateDataEntityList, corpid, WriteRequest.RefreshPolicy.NONE);
                    break;
                case CLUE:
                    clueModel.updateBatch(updateDataEntityList, corpid, WriteRequest.RefreshPolicy.NONE);
                    break;
                default:
                    throw new XbbException(ScoreErrorCodeEnum.API_ERROR_1200001, ScoreErrorCodeEnum.API_ERROR_1200001.getMsg());
            }
        }
    }


}
